#!/usr/bin/env python
# -*- coding: utf-8 -*-
# --------------------------------------------------------------------------
# Software:     InVesalius - Software de Reconstrucao 3D de Imagens Medicas
# Copyright:    (C) 2001  Centro de Pesquisas Renato Archer
# Homepage:     http://www.softwarepublico.gov.br
# Contact:      invesalius@cti.gov.br
# License:      GNU - GPL 2 (LICENSE.txt/LICENCA.txt)
# --------------------------------------------------------------------------
#    Este programa e software livre; voce pode redistribui-lo e/ou
#    modifica-lo sob os termos da Licenca Publica Geral GNU, conforme
#    publicada pela Free Software Foundation; de acordo com a versao 2
#    da Licenca.
#
#    Este programa eh distribuido na expectativa de ser util, mas SEM
#    QUALQUER GARANTIA; sem mesmo a garantia implicita de
#    COMERCIALIZACAO ou de ADEQUACAO A QUALQUER PROPOSITO EM
#    PARTICULAR. Consulte a Licenca Publica Geral GNU para obter mais
#    detalhes.
# --------------------------------------------------------------------------
import os
import collections
import itertools
import tempfile

import numpy as np

import vtk
from vtk.wx.wxVTKRenderWindowInteractor import wxVTKRenderWindowInteractor

import invesalius.data.styles as styles
import wx
import sys
from invesalius.pubsub import pub as Publisher

try:
    from agw import floatspin as FS
except ImportError:  # if it's not there locally, try the wxPython lib.
    import wx.lib.agw.floatspin as FS

import invesalius.constants as const
import invesalius.data.cursor_actors as ca
import invesalius.data.slice_ as sl
import invesalius.data.vtk_utils as vtku
import invesalius.project as project
import invesalius.data.slice_data as sd
import invesalius.utils as utils
import invesalius.session as ses
import invesalius.data.converters as converters
import invesalius.data.measures as measures

ID_TO_TOOL_ITEM = {}
STR_WL = "WL: %d  WW: %d"

ORIENTATIONS = {
    "AXIAL": const.AXIAL,
    "CORONAL": const.CORONAL,
    "SAGITAL": const.SAGITAL,
}


class HeartResliceViewer(wx.Panel):
    def __init__(self, parent):
        wx.Panel.__init__(self, parent, size=wx.Size(320, 300))
        # Interactor additional style
        self.style = None
        self.last_position_mouse_move = ()
        self.state = const.STATE_DEFAULT
        # All renderers and image actors in this viewer
        self.slice_data_list = []
        self.slice_data = None

        self.slice_actor = None
        self.interpolation_slice_status = True

        self.scroll_enabled = True
        self.__init_gui()
        # VTK pipeline and actors
        # self.__config_interactor()
        self.cross_actor = vtk.vtkActor()

        # self.__bind_events()
        self.__bind_events_wx()
        self.testviewer()

    def __init_gui(self):
        self.interactor = wxVTKRenderWindowInteractor(self, -1, size=self.GetSize())

        scroll = wx.ScrollBar(self, -1, style=wx.SB_VERTICAL)
        self.scroll = scroll

        sizer = wx.BoxSizer(wx.HORIZONTAL)
        sizer.Add(self.interactor, 1, wx.EXPAND | wx.GROW)
        sizer.Add(scroll, 0, wx.EXPAND | wx.GROW)
        background_sizer = wx.BoxSizer(wx.VERTICAL)
        background_sizer.AddSizer(sizer, 1, wx.EXPAND | wx.GROW | wx.ALL, 2)
        self.SetSizer(background_sizer)
        background_sizer.Fit(self)

        self.Layout()
        self.Update()
        self.SetAutoLayout(1)

        self.pick = vtk.vtkWorldPointPicker()
        self.interactor.SetPicker(self.pick)

    # def __config_interactor(self):
    #     style = vtk.vtkInteractorStyleImage()

    #     interactor = self.interactor
    #     interactor.SetInteractorStyle(style)

    def SetInteractorStyle(self, state):
        cleanup = getattr(self.style, 'CleanUp', None)
        if cleanup:
            self.style.CleanUp()

        del self.style

        style = styles.get_style(state)(self)
        print('hr:116;style:', style)
        setup = getattr(style, 'SetUp', None)
        if setup:
            style.SetUp()

        self.style = style
        self.interactor.SetInteractorStyle(style)
        self.interactor.Render()

        self.state = state

    def __bind_events_wx(self):
        self.scroll.Bind(wx.EVT_SCROLL, self.OnScrollBar)
        self.scroll.Bind(wx.EVT_SCROLL_THUMBTRACK, self.OnScrollBarRelease)
        self.interactor.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
        self.interactor.Bind(wx.EVT_RIGHT_UP, self.OnContextMenu)
        self.interactor.Bind(wx.EVT_SIZE, self.OnSize)

    def testviewer(self):
        reader = vtk.vtkTIFFReader()
        reader.SetFilePrefix('E:/InVesalius/data/tif_sample/')
        reader.SetFilePattern('%s%d.tif')
        # reader.SetFileDimensionality(3)
        reader.SetDataExtent(0, 63, 0, 63, 1, 50)
        reader.SetDataSpacing(3.2, 3.2, 0.5)
        reader.SetDataOrigin(0, 0, 0)
        reader.SetDataScalarTypeToUnsignedShort()
        reader.UpdateWholeExtent()
        # #------------------------------------------------------------
        # Calculate the center of the volume
        reader.Update()
        center = reader.GetOutput().GetCenter()
        # print(center)
        # Matrices for axial, coronal, sagittal, oblique view orientations
        aim = np.array([0, 0, 1])
        apex = np.array([0, 0, 0])
        base = np.array([1, 1, 0])
        axis = base - apex
        raxis = np.array([axis[1] * aim[2] - axis[2] * aim[1],
                          axis[2] * aim[0] - axis[0] * aim[2],
                          axis[0] * aim[1] - axis[1] * aim[0]])
        if raxis.all() == 0:
            x, y, z = raxis
        else:
            raxis = raxis / np.sqrt(raxis.dot(raxis))
            x, y, z = raxis

        cos_angle = aim.dot(axis) / np.sqrt(axis.dot(axis))
        sin_angle = np.sqrt(1 - cos_angle * cos_angle)
        cosine = [x * x * (1 - cos_angle) + cos_angle, x * y * (1 - cos_angle) + z * sin_angle,
                  x * z * (1 - cos_angle) - y * sin_angle,
                  x * y * (1 - cos_angle) - z * sin_angle, y * y * (1 - cos_angle) + cos_angle,
                  y * z * (1 - cos_angle) + x * sin_angle,
                  x * z * (1 - cos_angle) + y * sin_angle, y * z * (1 - cos_angle) - x * sin_angle,
                  z * z * (1 - cos_angle) + cos_angle]
        # remat = vtk.vtkMatrix4x4()
        # remat.DeepCopy((x*x*(1-cos_angle)+cos_angle,x*y*(1-cos_angle)+z*sin_angle,
        #                 x*z*(1-cos_angle)-y*sin_angle,center[0],
        #                 x*y*(1-cos_angle)-z*sin_angle,y*y*(1-cos_angle)+cos_angle,
        #                 y*z*(1-cos_angle)+x*sin_angle,center[1],
        #                 x*z*(1-cos_angle)+y*sin_angle,y*z*(1-cos_angle)-x*sin_angle,
        #                 z*z*(1-cos_angle)+cos_angle,center[2],
        #                 0,0,0,1 ))
        # axial = vtk.vtkMatrix4x4()

        # axial.DeepCopy((1, 0, 0, center[0],
        #                 0, 1, 0, center[1],
        #                 0, 0, 1, center[2],
        #                 0, 0, 0, 1))

        # coronal = vtk.vtkMatrix4x4()
        # coronal.DeepCopy((1, 0, 0, center[0],
        #                   0, 0, 1, center[1],
        #                   0,-1, 0, center[2],
        #                   0, 0, 0, 1))

        # sagittal = vtk.vtkMatrix4x4()
        # sagittal.DeepCopy((0, 0,-1, center[0],
        #                    1, 0, 0, center[1],
        #                    0,-1, 0, center[2],
        #                    0, 0, 0, 1))

        # oblique = vtk.vtkMatrix4x4()
        # oblique.DeepCopy((1, 0, 0, center[0],
        #                   0, 0.866025, -0.5, center[1],
        #                   0, 0.5, 0.866025, center[2],
        #                   0, 0, 0, 1))

        # Extract a slice in the desired orientation

        reslice = vtk.vtkImageReslice()
        reslice.SetInputConnection(reader.GetOutputPort())

        reslice.SetResliceAxesDirectionCosines(cosine)
        reslice.SetResliceAxesOrigin(center)
        # # reslice.SetResliceAxes(oblique)
        # # reslice.SetResliceAxes(remat)
        # # reslice.SetResliceAxes(coronal)
        # # reslice.SetResliceAxes(sagittal)
        reslice.SetOutputDimensionality(2)
        reslice.SetInterpolationModeToLinear()
        self.reslice = reslice

        # Create a greyscale lookup table
        table = vtk.vtkLookupTable()
        table.SetRange(0, 255)  # image intensity range
        table.SetValueRange(0.0, 1.0)  # from black to white
        table.SetSaturationRange(0.0, .5)  # no color saturation
        table.SetRampToLinear()

        table.Build()

        # Map the image through the lookup table
        color = vtk.vtkImageMapToColors()
        color.SetLookupTable(table)
        color.SetInputConnection(self.reslice.GetOutputPort())

        # Display the image
        # mapper = vtk.vtkPolyDataMapper()
        # mapper.SetInputConnection(color.GetOutputPort())
        actor = vtk.vtkImageActor()
        actor.GetMapper().SetInputConnection(color.GetOutputPort())
        # actor.GetMapper().SetInputConnection(color.GetOutputPort())

        renderer = vtk.vtkRenderer()
        renderer.AddActor(actor)
        renderer.SetBackground(0, 0, 0)
        self.interactor.GetRenderWindow().AddRenderer(renderer)
        # Set up the interaction
        self.interactorStyle = vtk.vtkInteractorStyleImage()
        # interactor = vtk.vtkRenderWindowInteractor()
        # Create callbacks for slicing the image
        self.actions = {}
        self.actions["Slicing"] = 0
        self.interactorStyle.AddObserver("MouseMoveEvent", self.MouseMoveCallback)
        self.interactorStyle.AddObserver("LeftButtonPressEvent", self.ButtonCallback)
        self.interactorStyle.AddObserver("LeftButtonReleaseEvent", self.ButtonCallback)

        self.interactor.SetInteractorStyle(self.interactorStyle)
        self.interactor.Render()

    def ButtonCallback(self, obj, event):
        if event == "LeftButtonPressEvent":
            self.actions["Slicing"] = 1
        else:
            self.actions["Slicing"] = 0

    def MouseMoveCallback(self, obj, event):
        (lastX, lastY) = self.interactor.GetLastEventPosition()
        (mouseX, mouseY) = self.interactor.GetEventPosition()
        if self.actions["Slicing"] == 1:
            deltaY = mouseY - lastY
            self.reslice.Update()
            sliceSpacing = self.reslice.GetOutput().GetSpacing()[2]
            matrix = self.reslice.GetResliceAxes()
            # print 'matrix',matrix
            # move the center point that we are slicing through
            center = matrix.MultiplyPoint((0, 0, sliceSpacing * deltaY, 1))
            # print 'center:', center
            matrix.SetElement(0, 3, center[0])
            matrix.SetElement(1, 3, center[1])
            matrix.SetElement(2, 3, center[2])
            # print 'matrix:', matrix
            self.interactor.Render()
        else:
            self.interactorStyle.OnMouseMove()

    def OnContextMenu(self, evt):
        if (self.last_position_mouse_move == self.interactor.GetLastEventPosition()):
            self.menu.caller = self
            self.PopupMenu(self.menu)
        evt.Skip()

    def OnScrollBar(self, evt=None, update3D=True):
        pos = self.scroll.GetThumbPosition()
        print('HeartReslicePro.py-301-pos:', pos)
        pass
        # self.set_slice_number(pos)
        # if update3D:
        #     self.UpdateSlice3D(pos)
        # if self.state == const.SLICE_STATE_CROSS:
        #     # Update other slice's cross according to the new focal point from
        #     # the actual orientation.
        #     focal_point = self.cross.GetFocalPoint()
        #     Publisher.sendMessage('Update cross position', focal_point)
        #     Publisher.sendMessage('Update slice viewer')
        # else:
        #     self.interactor.Render()
        # if evt:
        #     if self._flush_buffer:
        #         self.slice_.apply_slice_buffer_to_mask(self.orientation)
        #     evt.Skip()

    def create_slice_window(self):
        renderer = vtk.vtkRenderer()
        renderer.SetLayer(0)
        cam = renderer.GetActiveCamera()

        canvas_renderer = vtk.vtkRenderer()
        canvas_renderer.SetLayer(1)
        canvas_renderer.SetActiveCamera(cam)
        canvas_renderer.SetInteractive(0)
        canvas_renderer.PreserveDepthBufferOn()

        overlay_renderer = vtk.vtkRenderer()
        overlay_renderer.SetLayer(2)
        overlay_renderer.SetActiveCamera(cam)
        overlay_renderer.SetInteractive(0)

        self.interactor.GetRenderWindow().SetNumberOfLayers(3)
        self.interactor.GetRenderWindow().AddRenderer(overlay_renderer)
        self.interactor.GetRenderWindow().AddRenderer(canvas_renderer)
        self.interactor.GetRenderWindow().AddRenderer(renderer)

    def OnScrollBarRelease(self, evt):
        pos = self.scroll.GetThumbPosition()
        evt.Skip()

    def OnKeyDown(self, evt=None, obj=None):
        pos = self.scroll.GetThumbPosition()
        skip = True

        min = 0
        max = self.slice_.GetMaxSliceNumber(self.orientation)

        projections = {wx.WXK_NUMPAD0: const.PROJECTION_NORMAL,
                       wx.WXK_NUMPAD1: const.PROJECTION_MaxIP,
                       wx.WXK_NUMPAD2: const.PROJECTION_MinIP,
                       wx.WXK_NUMPAD3: const.PROJECTION_MeanIP,
                       wx.WXK_NUMPAD4: const.PROJECTION_MIDA,
                       wx.WXK_NUMPAD5: const.PROJECTION_CONTOUR_MIP,
                       wx.WXK_NUMPAD6: const.PROJECTION_CONTOUR_MIDA, }

        if self._flush_buffer:
            # self.slice_.apply_slice_buffer_to_mask(self.orientation)
            pass

        if (evt.GetKeyCode() == wx.WXK_UP and pos > min):
            self.OnScrollForward()
            self.OnScrollBar()

        elif (evt.GetKeyCode() == wx.WXK_DOWN and pos < max):
            self.OnScrollBackward()
            self.OnScrollBar()

        elif (evt.GetKeyCode() == wx.WXK_NUMPAD_ADD):
            actual_value = self.mip_ctrls.mip_size_spin.GetValue()
            self.mip_ctrls.mip_size_spin.SetValue(actual_value + 1)
            if self.mip_ctrls.mip_size_spin.GetValue() != actual_value:
                self.number_slices = self.mip_ctrls.mip_size_spin.GetValue()
                self.ReloadActualSlice()

        elif (evt.GetKeyCode() == wx.WXK_NUMPAD_SUBTRACT):
            actual_value = self.mip_ctrls.mip_size_spin.GetValue()
            self.mip_ctrls.mip_size_spin.SetValue(actual_value - 1)
            if self.mip_ctrls.mip_size_spin.GetValue() != actual_value:
                self.number_slices = self.mip_ctrls.mip_size_spin.GetValue()
                self.ReloadActualSlice()

        elif evt.GetKeyCode() in projections:
            self.slice_.SetTypeProjection(projections[evt.GetKeyCode()])
            Publisher.sendMessage('Set projection type', projections[evt.GetKeyCode()])
            Publisher.sendMessage('Reload actual slice')
            skip = False

        self.UpdateSlice3D(pos)
        self.interactor.Render()

        if evt and skip:
            evt.Skip()

    def OnScrollForward(self, evt=None, obj=None):
        pass
        # if not self.scroll_enabled:
        #     return
        # pos = self.scroll.GetThumbPosition()
        # min = 0

        # if(pos > min):
        #     if self._flush_buffer:
        #         # self.slice_.apply_slice_buffer_to_mask(self.orientation)
        #     pos = pos - 1
        #     self.scroll.SetThumbPosition(pos)
        #     self.OnScrollBar()

    def OnScrollBackward(self, evt=None, obj=None):
        pass
        # if not self.scroll_enabled:
        #     return
        # pos = self.scroll.GetThumbPosition()
        # # max = self.slice_.GetMaxSliceNumber(self.orientation)

        # if(pos < max):
        #     if self._flush_buffer:
        #         self.slice_.apply_slice_buffer_to_mask(self.orientation)
        #     pos = pos + 1
        #     self.scroll.SetThumbPosition(pos)
        #     self.OnScrollBar()

    def OnSize(self, evt):
        w, h = evt.GetSize()
        w = float(w)
        h = float(h)
        if self.slice_data:
            self.slice_data.SetSize((w, h))
        evt.Skip()


class ModelResultViewer(wx.Panel):
    def __init__(self, parent):
        wx.Panel.__init__(self, parent)

        sizer = wx.BoxSizer(wx.HORIZONTAL)
        sizer.Fit(self)

        self.SetSizer(sizer)
        self.Update()
        self.SetAutoLayout(1)


class LeftVentricular17Segment(wx.Panel):
    def __init__(self, parent):
        wx.Panel.__init__(self, parent)

        sizer = wx.BoxSizer(wx.HORIZONTAL)
        sizer.Add(VentricularSegement(self, -1), 1, wx.EXPAND | wx.GROW)
        sizer.Add(VenSegeToolPanel(self), 0, wx.EXPAND | wx.GROW)
        sizer.Fit(self)

        self.SetSizer(sizer)
        self.Update()
        self.SetAutoLayout(1)


class VentricularSegement(wx.Panel):
    def __init__(self, parent, id):
        super(VentricularSegement, self).__init__(parent, id)
        # self.can_show_raycasting_widget = 0
        self.__init_aui_manager()
        # sizer = wx.BoxSizer(wx.HORIZONTAL)
        # sizer.Add(volume_viewer.Viewer(self), 1, wx.EXPAND|wx.GROW)
        # self.SetSizer(sizer)
        # self.__bind_events()
        # self.__bind_events_wx()
        # sizer.Fit(self)

    def __init_aui_manager(self):
        self.aui_manager = wx.aui.AuiManager()
        self.aui_manager.SetManagedWindow(self)

        # p1 = volume_viewer.Viewer(self)
        # s1 = wx.aui.AuiPaneInfo().Centre().\
        #         CloseButton(False).MaximizeButton(False).CaptionVisible(0)
        # self.s1 = s1

        # self.clut_raycasting = CLUTRaycastingWidget(self, -1)
        # self.s2 = wx.aui.AuiPaneInfo().Bottom().BestSize((200, 200)).\
        #         CloseButton(False).MaximizeButton(False).CaptionVisible(0).\
        #         Hide()

        # self.aui_manager.AddPane(p1, s1)
        # self.aui_manager.AddPane(self.clut_raycasting, self.s2)
        self.aui_manager.Update()


class VenSegeToolPanel(wx.Panel):
    def __init__(self, parent):
        wx.Panel.__init__(self, parent)

        # sizer = wx.BoxSizer(wx.VERTICAL)
        # sizer.Add(button_colour, 0, wx.ALL, sp)
        # sizer.Add(button_view, 0, wx.TOP|wx.BOTTOM, 1)

        # sizer.Add(button_stereo, 0, wx.TOP|wx.BOTTOM, 1)

        # self.SetSizer(sizer)
        self.SetAutoLayout(1)
        self.Update()
        self.Refresh()


class HistogramViewer(wx.Panel):
    def __init__(self, parent):
        wx.Panel.__init__(self, parent)

        sizer = wx.BoxSizer(wx.HORIZONTAL)
        sizer.Fit(self)

        self.SetSizer(sizer)
        self.Update()
        self.SetAutoLayout(1)


class ContourMIPConfig(wx.Panel):
    def __init__(self, prnt, orientation):
        wx.Panel.__init__(self, prnt)
        self.mip_size_spin = wx.SpinCtrl(self, -1, min=1, max=240,
                                         initial=const.PROJECTION_MIP_SIZE)
        self.mip_size_spin.SetValue(const.PROJECTION_MIP_SIZE)
        self.mip_size_spin.SetToolTip(wx.ToolTip(_("Number of slices used to compound the visualization.")))
        w, h = self.mip_size_spin.GetTextExtent('M')
        self.mip_size_spin.SetMinSize((5 * w + 10, -1))
        self.mip_size_spin.SetMaxSize((5 * w + 10, -1))

        self.border_spin = FS.FloatSpin(self, -1, min_val=0, max_val=10,
                                        increment=0.1,
                                        value=const.PROJECTION_BORDER_SIZE,
                                        digits=1, agwStyle=FS.FS_LEFT)
        self.border_spin.SetToolTip(wx.ToolTip(_("Controls the sharpness of the"
                                                 " contour. The greater the"
                                                 " value, the sharper the"
                                                 " contour.")))
        w, h = self.border_spin.GetTextExtent('M')
        self.border_spin.SetMinSize((5 * w + 10, -1))
        self.border_spin.SetMaxSize((5 * w + 10, -1))

        self.inverted = wx.CheckBox(self, -1, _("Inverted order"))
        self.inverted.SetToolTip(wx.ToolTip(_("If checked, the slices are"
                                              " traversed in descending"
                                              " order to compound the"
                                              " visualization instead of"
                                              " ascending order.")))

        txt_mip_size = wx.StaticText(self, -1, _("Number of slices"), style=wx.ALIGN_CENTER_HORIZONTAL)
        self.txt_mip_border = wx.StaticText(self, -1, _("Sharpness"))

        sizer = wx.BoxSizer(wx.HORIZONTAL)
        sizer.Add(txt_mip_size, 0, wx.EXPAND | wx.ALL, 2)
        sizer.Add(self.mip_size_spin, 0, wx.EXPAND)
        sizer.AddSpacer((10, 0))
        sizer.Add(self.txt_mip_border, 0, wx.EXPAND | wx.ALL, 2)
        sizer.Add(self.border_spin, 0, wx.EXPAND)
        sizer.AddSpacer((10, 0))
        sizer.Add(self.inverted, 0, wx.EXPAND)
        self.SetSizer(sizer)
        sizer.Fit(self)

        self.Layout()
        self.Update()
        self.SetAutoLayout(1)

        self.orientation = orientation
        self.canvas = None

        self.mip_size_spin.Bind(wx.EVT_SPINCTRL, self.OnSetMIPSize)
        self.border_spin.Bind(wx.EVT_SPINCTRL, self.OnSetMIPBorder)
        self.inverted.Bind(wx.EVT_CHECKBOX, self.OnCheckInverted)

        Publisher.subscribe(self._set_projection_type, 'Set projection type')

    def OnSetMIPSize(self, evt):
        val = self.mip_size_spin.GetValue()
        Publisher.sendMessage('Set MIP size %s' % self.orientation, val)

    def OnSetMIPBorder(self, evt):
        val = self.border_spin.GetValue()
        Publisher.sendMessage('Set MIP border %s' % self.orientation, val)

    def OnCheckInverted(self, evt):
        val = self.inverted.GetValue()
        Publisher.sendMessage('Set MIP Invert %s' % self.orientation, val)

    def _set_projection_type(self, pubsub_evt):
        tprojection = pubsub_evt.data

        if tprojection in (const.PROJECTION_MIDA,
                           const.PROJECTION_CONTOUR_MIDA):
            self.inverted.Enable()
        else:
            self.inverted.Disable()

        if tprojection in (const.PROJECTION_CONTOUR_MIP,
                           const.PROJECTION_CONTOUR_MIDA):
            self.border_spin.Enable()
            self.txt_mip_border.Enable()
        else:
            self.border_spin.Disable()
            self.txt_mip_border.Disable()


class CanvasRendererCTX:
    def __init__(self, evt_renderer, canvas_renderer, orientation=None):
        """
        A Canvas to render over a vtktRenderer.

        Params:
            evt_renderer: a vtkRenderer which this class is going to watch for
                any render event to update the canvas content.
            canvas_renderer: the vtkRenderer where the canvas is going to be
                added.

        This class uses wx.GraphicsContext to render to a vtkImage.

        TODO: Verify why in Windows the color are strange when using transparency.
        TODO: Add support to evento (ex. click on a square)
        """
        self.canvas_renderer = canvas_renderer
        self.evt_renderer = evt_renderer
        self._size = self.canvas_renderer.GetSize()
        self.draw_list = []
        self.orientation = orientation
        self.gc = None
        self.last_cam_modif_time = -1
        self.modified = True
        self._drawn = False
        self._init_canvas()
        evt_renderer.AddObserver("StartEvent", self.OnPaint)

    def _init_canvas(self):
        w, h = self._size
        self._array = np.zeros((h, w, 4), dtype=np.uint8)

        self._cv_image = converters.np_rgba_to_vtk(self._array)

        self.mapper = vtk.vtkImageMapper()
        self.mapper.SetInputData(self._cv_image)
        self.mapper.SetColorWindow(255)
        self.mapper.SetColorLevel(128)

        self.actor = vtk.vtkActor2D()
        self.actor.SetPosition(0, 0)
        self.actor.SetMapper(self.mapper)
        self.actor.GetProperty().SetOpacity(0.99)

        self.canvas_renderer.AddActor2D(self.actor)

        self.rgb = np.zeros((h, w, 3), dtype=np.uint8)
        self.alpha = np.zeros((h, w, 1), dtype=np.uint8)

        self.bitmap = wx.EmptyBitmapRGBA(w, h)
        self.image = wx.ImageFromBuffer(w, h, self.rgb, self.alpha)

    def _resize_canvas(self, w, h):
        self._array = np.zeros((h, w, 4), dtype=np.uint8)
        self._cv_image = converters.np_rgba_to_vtk(self._array)
        self.mapper.SetInputData(self._cv_image)
        self.mapper.Update()

        self.rgb = np.zeros((h, w, 3), dtype=np.uint8)
        self.alpha = np.zeros((h, w, 1), dtype=np.uint8)

        self.bitmap = wx.EmptyBitmapRGBA(w, h)
        self.image = wx.ImageFromBuffer(w, h, self.rgb, self.alpha)

        self.modified = True

    def remove_from_renderer(self):
        self.canvas_renderer.RemoveActor(self.actor)
        self.evt_renderer.RemoveObservers("StartEvent")

    def OnPaint(self, evt, obj):
        size = self.canvas_renderer.GetSize()
        w, h = size
        if self._size != size:
            self._size = size
            self._resize_canvas(w, h)

        cam_modif_time = self.evt_renderer.GetActiveCamera().GetMTime()
        if (not self.modified) and cam_modif_time == self.last_cam_modif_time:
            return

        self.last_cam_modif_time = cam_modif_time

        self._array[:] = 0

        coord = vtk.vtkCoordinate()

        self.image.SetDataBuffer(self.rgb)
        self.image.SetAlphaBuffer(self.alpha)
        self.image.Clear()
        gc = wx.GraphicsContext.Create(self.image)
        if sys.platform != 'darwin':
            gc.SetAntialiasMode(0)

        self.gc = gc

        font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT)
        #  font.SetWeight(wx.BOLD)
        font = gc.CreateFont(font, (0, 0, 255))
        gc.SetFont(font)

        pen = wx.Pen(wx.Colour(255, 0, 0, 128), 2, wx.SOLID)
        brush = wx.Brush(wx.Colour(0, 255, 0, 128))
        gc.SetPen(pen)
        gc.SetBrush(brush)
        gc.Scale(1, -1)

        for d in self.draw_list:
            d.draw_to_canvas(gc, self)

        gc.Destroy()

        self.gc = None

        if self._drawn:
            self.bitmap = self.image.ConvertToBitmap()
            self.bitmap.CopyToBuffer(self._array, wx.BitmapBufferFormat_RGBA)

        self._cv_image.Modified()
        self.modified = False
        self._drawn = False

    def calc_text_size(self, text, font=None):
        """
        Given an unicode text and a font returns the width and height of the
        rendered text in pixels.

        Params:
            text: An unicode text.
            font: An wxFont.

        Returns:
            A tuple with width and height values in pixels
        """
        if self.gc is None:
            return None
        gc = self.gc

        if font is None:
            font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT)

        _font = gc.CreateFont(font)
        gc.SetFont(_font)
        w, h = gc.GetTextExtent(text)
        return w, h

    def draw_line(self, pos0, pos1, arrow_start=False, arrow_end=False, colour=(255, 0, 0, 128), width=2,
                  style=wx.SOLID):
        """
        Draw a line from pos0 to pos1

        Params:
            pos0: the start of the line position (x, y).
            pos1: the end of the line position (x, y).
            arrow_start: if to draw a arrow at the start of the line.
            arrow_end: if to draw a arrow at the end of the line.
            colour: RGBA line colour.
            width: the width of line.
            style: default wx.SOLID.
        """
        if self.gc is None:
            return None
        gc = self.gc

        p0x, p0y = pos0
        p1x, p1y = pos1

        p0y = -p0y
        p1y = -p1y

        pen = wx.Pen(wx.Colour(*colour), width, wx.SOLID)
        pen.SetCap(wx.CAP_BUTT)
        gc.SetPen(pen)

        path = gc.CreatePath()
        path.MoveToPoint(p0x, p0y)
        path.AddLineToPoint(p1x, p1y)
        gc.StrokePath(path)

        font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT)
        font = gc.CreateFont(font)
        gc.SetFont(font)
        w, h = gc.GetTextExtent("M")

        p0 = np.array((p0x, p0y))
        p3 = np.array((p1x, p1y))
        if arrow_start:
            v = p3 - p0
            v = v / np.linalg.norm(v)
            iv = np.array((v[1], -v[0]))
            p1 = p0 + w * v + iv * w / 2.0
            p2 = p0 + w * v + (-iv) * w / 2.0

            path = gc.CreatePath()
            path.MoveToPoint(p0)
            path.AddLineToPoint(p1)
            path.MoveToPoint(p0)
            path.AddLineToPoint(p2)
            gc.StrokePath(path)

        if arrow_end:
            v = p3 - p0
            v = v / np.linalg.norm(v)
            iv = np.array((v[1], -v[0]))
            p1 = p3 - w * v + iv * w / 2.0
            p2 = p3 - w * v + (-iv) * w / 2.0

            path = gc.CreatePath()
            path.MoveToPoint(p3)
            path.AddLineToPoint(p1)
            path.MoveToPoint(p3)
            path.AddLineToPoint(p2)
            gc.StrokePath(path)

        self._drawn = True

    def draw_circle(self, center, radius, width=2, line_colour=(255, 0, 0, 128), fill_colour=(0, 0, 0, 0)):
        """
        Draw a circle centered at center with the given radius.

        Params:
            center: (x, y) position.
            radius: float number.
            width: line width.
            line_colour: RGBA line colour
            fill_colour: RGBA fill colour.
        """
        if self.gc is None:
            return None
        gc = self.gc

        pen = wx.Pen(wx.Colour(*line_colour), width, wx.SOLID)
        gc.SetPen(pen)

        brush = wx.Brush(wx.Colour(*fill_colour))
        gc.SetBrush(brush)

        cx, cy = center
        cy = -cy

        path = gc.CreatePath()
        path.AddCircle(cx, cy, 2.5)
        gc.StrokePath(path)
        gc.FillPath(path)
        self._drawn = True

    def draw_rectangle(self, pos, width, height, line_colour=(255, 0, 0, 128), fill_colour=(0, 0, 0, 0)):
        """
        Draw a rectangle with its top left at pos and with the given width and height.

        Params:
            pos: The top left pos (x, y) of the rectangle.
            width: width of the rectangle.
            height: heigth of the rectangle.
            line_colour: RGBA line colour.
            fill_colour: RGBA fill colour.
        """
        if self.gc is None:
            return None
        gc = self.gc

        px, py = pos
        gc.SetPen(wx.Pen(line_colour))
        gc.SetBrush(wx.Brush(fill_colour))
        gc.DrawRectangle(px, py, width, height)
        self._drawn = True

    def draw_text(self, text, pos, font=None, txt_colour=(255, 255, 255)):
        """
        Draw text.

        Params:
            text: an unicode text.
            pos: (x, y) top left position.
            font: if None it'll use the default gui font.
            txt_colour: RGB text colour
        """
        if self.gc is None:
            return None
        gc = self.gc

        if font is None:
            font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT)

        font = gc.CreateFont(font, txt_colour)
        gc.SetFont(font)

        px, py = pos
        py = -py

        gc.DrawText(text, px, py)
        self._drawn = True

    def draw_text_box(self, text, pos, font=None, txt_colour=(255, 255, 255), bg_colour=(128, 128, 128, 128), border=5):
        """
        Draw text inside a text box.

        Params:
            text: an unicode text.
            pos: (x, y) top left position.
            font: if None it'll use the default gui font.
            txt_colour: RGB text colour
            bg_colour: RGBA box colour
            border: the border size.
        """
        if self.gc is None:
            return None
        gc = self.gc

        if font is None:
            font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT)

        _font = gc.CreateFont(font, txt_colour)
        gc.SetFont(_font)
        w, h = gc.GetTextExtent(text)

        px, py = pos

        # Drawing the box
        cw, ch = w + border * 2, h + border * 2
        self.draw_rectangle((px, -py), cw, ch, bg_colour, bg_colour)

        # Drawing the text
        tpx, tpy = px + border, py - border
        self.draw_text(text, (tpx, tpy), font, txt_colour)
        self._drawn = True

    def draw_arc(self, center, p0, p1, line_colour=(255, 0, 0, 128), width=2):
        """
        Draw an arc passing in p0 and p1 centered at center.

        Params:
            center: (x, y) center of the arc.
            p0: (x, y).
            p1: (x, y).
            line_colour: RGBA line colour.
            width: width of the line.
        """
        if self.gc is None:
            return None
        gc = self.gc
        pen = wx.Pen(wx.Colour(*line_colour), width, wx.SOLID)
        gc.SetPen(pen)

        c = np.array(center)
        v0 = np.array(p0) - c
        v1 = np.array(p1) - c

        c[1] = -c[1]
        v0[1] = -v0[1]
        v1[1] = -v1[1]

        s0 = np.linalg.norm(v0)
        s1 = np.linalg.norm(v1)

        a0 = np.arctan2(v0[1], v0[0])
        a1 = np.arctan2(v1[1], v1[0])

        if (a1 - a0) % (np.pi * 2) < (a0 - a1) % (np.pi * 2):
            sa = a0
            ea = a1
        else:
            sa = a1
            ea = a0

        path = gc.CreatePath()
        path.AddArc((c[0], c[1]), min(s0, s1), sa, ea)
        gc.StrokePath(path)
        self._drawn = True
